﻿using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Drawing.Text;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Xml;
using System.Web;
using ESRI.ArcGIS.ADF.ArcGISServer;
using WorldViewSolutions.ArcGISServer.SOE.DataSources;
using ESRI.ArcGIS.Server;

namespace WorldViewSolutions.ArcGISServer.SOE
{

    /// <summary>
    /// 
    /// </summary>
    /// <remarks></remarks>
	public static class MapExport
	{
		#region [ Public Methods ]

        /// <summary>
        /// Saves to file.
        /// </summary>
        /// <param name="image">The image.</param>
        /// <param name="format">The format.</param>
        /// <param name="outputDirectory">The output directory.</param>
        /// <returns></returns>
        /// <remarks></remarks>
        public static string SaveToFile(Bitmap image, ExportFormat format, string outputDirectory)
        {
            string path;
            string fileName = "_ags_" + Guid.NewGuid().ToString().Replace("-", "");

            System.Drawing.Imaging.ImageFormat imageFormat = System.Drawing.Imaging.ImageFormat.Jpeg;

            long jpegQuality = 75L;

            switch (format)
            {
                case ExportFormat.GIF:
                    fileName += ".gif";
                    imageFormat = System.Drawing.Imaging.ImageFormat.Gif;
                    break;
                case ExportFormat.PNG:
                    fileName += ".png";
                    imageFormat = System.Drawing.Imaging.ImageFormat.Png;
                    break;
                case ExportFormat.JPG:
                    fileName += ".jpg";
                    imageFormat = System.Drawing.Imaging.ImageFormat.Jpeg;
                    jpegQuality = 75L;
                    break;
                case ExportFormat.JPG100:
                    fileName += ".jpg";
                    imageFormat = System.Drawing.Imaging.ImageFormat.Jpeg;
                    jpegQuality = 100L;
                    break;
                default:
                    throw new Exception("Not a valid export format.");
            }


            path = System.IO.Path.Combine(outputDirectory, fileName);

            //HttpContext.Current.Trace.Write("MapExport", "Saving image as " + path);
            if (imageFormat == System.Drawing.Imaging.ImageFormat.Jpeg)
            {
                ImageCodecInfo jgpEncoder = GetEncoder(imageFormat);

                // Create an Encoder object based on the GUID
                // for the Quality parameter category.
                System.Drawing.Imaging.Encoder myEncoder = System.Drawing.Imaging.Encoder.Quality;

                // Create an EncoderParameters object.
                // An EncoderParameters object has an array of EncoderParameter
                // objects. In this case, there is only one
                // EncoderParameter object in the array.
                EncoderParameters myEncoderParameters = new EncoderParameters(1);
                EncoderParameter myEncoderParameter = new EncoderParameter(myEncoder, jpegQuality);
                myEncoderParameters.Param[0] = myEncoderParameter;
                image.Save(path, jgpEncoder, myEncoderParameters);
            }
            else
            {
                image.Save(path, imageFormat);
            }

            return fileName;
        }

        /// <summary>
        /// Creates the bitmap.
        /// </summary>
        /// <param name="exportParameters">The export parameters.</param>
        /// <returns></returns>
        /// <remarks></remarks>
        public static Bitmap CreateBitmap(ExportParameters exportParameters)
        {
            //HttpContext.Current.Trace.Write("MapExport.CreateBitmap", "BEGIN");

            int widthPixels = Convert.ToInt32(Math.Round(exportParameters.Width * exportParameters.DPI));
            int heightPixels = Convert.ToInt32(Math.Round(exportParameters.Height * exportParameters.DPI));
            exportParameters.FixExent();

            //HttpContext.Current.Trace.Warn("MapExport.CreateBitmap", "widthPixels = " + widthPixels + ", heightPixels = " + heightPixels + ", exportParameters.DPI = " + exportParameters.DPI);

            Bitmap mapImageMerged = null;

            List<Bitmap> imageBitmaps = new List<Bitmap>();
            List<float> imageTransparencies = new List<float>();

            IMapDResource dynamicResource = null;
            for (int i = 0; i < exportParameters.Resources.Count; i++)
            {
                if (!exportParameters.Resources[i].Resource.IsCached)
                {
                    dynamicResource = exportParameters.Resources[i].Resource;
                    break;
                }
                else
                {
                }
            }

            
            // create each map layer
            foreach (ExportMapResource resource in exportParameters.Resources)
            {
                //HttpContext.Current.Trace.Write("Exporting Resource: " + resource.Name);
                Bitmap image = resource.Resource.DrawExtent(exportParameters.Width, exportParameters.Height, exportParameters.DPI, resource.DPI, exportParameters.Extent);

                if (image != null)
                {
                    image.SetResolution(exportParameters.DPI, exportParameters.DPI);
                    imageBitmaps.Add(image);

                    // remember transparency of image for merging later
                    imageTransparencies.Add(Convert.ToSingle(resource.Opacity));

                    //HttpContext.Current.Trace.Write(String.Format("Saving resource opacity as {1}.  IsCached = {0}", resource.Resource.IsCached, resource.Opacity));
                }
            }

            // create the background color/image
            Bitmap bgImage = new Bitmap(widthPixels, heightPixels);
            bgImage.SetResolution(exportParameters.DPI, exportParameters.DPI);
            imageTransparencies.Add(1f);
            System.Drawing.Graphics graphics = System.Drawing.Graphics.FromImage(bgImage);
            SolidBrush bgBrush = new SolidBrush(exportParameters.BackgroundColor);
            graphics.FillRectangle(bgBrush, 0, 0, widthPixels, heightPixels);
            imageBitmaps.Add(bgImage);

            // create a merged bitmap from all Map resource layers
            if (imageBitmaps.Count > 0)
            {
                mapImageMerged = MergeImages(imageBitmaps, imageTransparencies);
            }

            Bitmap graphicsImage = DrawMarkup(exportParameters);
            MergeImage(mapImageMerged, graphicsImage, 1f);
            //HttpContext.Current.Trace.Write("MapExport.CreateBitmap", "END");

            return mapImageMerged;
        }


		#endregion

		#region [ Create Map Image Methods ]


        /// <summary>
        /// Draws the markup.
        /// </summary>
        /// <param name="exportParameters">The export parameters.</param>
        /// <returns></returns>
        /// <remarks></remarks>
        public static Bitmap DrawMarkup(ExportParameters exportParameters)
        {
            float dpiAdjuster = (float)(exportParameters.DPI / 96);
            int widthPixels = Convert.ToInt32(Math.Round(exportParameters.Width * exportParameters.DPI));
            int heightPixels = Convert.ToInt32(Math.Round(exportParameters.Height * exportParameters.DPI));

            Bitmap image = new Bitmap(widthPixels, heightPixels);

            ArcGISLog.Write("Graphics Count: " + exportParameters.Graphics.Count);
            foreach (WorldViewSolutions.Graphics.Graphic graghic in exportParameters.Graphics)
            {
                Bitmap graphicImage = new Bitmap(widthPixels, heightPixels);
                graphicImage.SetResolution(exportParameters.DPI, exportParameters.DPI);


                System.Drawing.Graphics markupGraphics = System.Drawing.Graphics.FromImage(graphicImage);
                markupGraphics.SmoothingMode = SmoothingMode.AntiAlias;

                GraphicsPath path = new GraphicsPath();

                GraphicsPath holePath;
                Region exteriorRingRegion;

                Pen strokePen = Pens.Blue;
                Brush fillBrush = Brushes.Blue;
                Brush textBrush = Brushes.Blue;

                float textSize = 8.0F;
                float strokeOpacity = 0.8f;
                float fillOpacity = 0.2f;

                // create the style
                if (graghic.Style != null)
                {
                    WorldViewSolutions.Graphics.Style style = graghic.Style;

                    strokePen = new Pen(System.Drawing.ColorTranslator.FromHtml(style.LineColor), (float)(Convert.ToDouble(style.LineWidth) * dpiAdjuster));
                    switch (style.LineType)
                    {
                        case "longdash":
                            strokePen.DashStyle = DashStyle.Dash;
                            strokePen.DashPattern = new float[] { 8.0F * dpiAdjuster, 2.0F * dpiAdjuster };
                            break;
                        case "shortdash":
                            strokePen.DashStyle = DashStyle.Dash;
                            strokePen.DashPattern = new float[] { 4.0F * dpiAdjuster, 2.0F * dpiAdjuster };
                            break;
                        case "dot":
                            strokePen.DashStyle = DashStyle.Dot;
                            break;
                        case "dashdot":
                            strokePen.DashStyle = DashStyle.DashDot;
                            break;
                    }

                    fillBrush = new SolidBrush(System.Drawing.ColorTranslator.FromHtml(style.FillColor));
                    textBrush = new SolidBrush(System.Drawing.ColorTranslator.FromHtml(style.LineColor));
                    strokeOpacity = Convert.ToSingle(style.LineOpacity);
                    fillOpacity = Convert.ToSingle(style.FillOpacity);
                    textSize = Convert.ToSingle(style.TextSize) * dpiAdjuster;
                }

                System.Drawing.Point p1;
                System.Drawing.Point p2;
                Rectangle rect;


                switch (graghic.Geometry.GetType().Name)
                {
                    case "Point":
                        WorldViewSolutions.Geometry.Point p = (WorldViewSolutions.Geometry.Point)graghic.Geometry;

                        if (graghic.Style.IconStyle != null && !String.IsNullOrEmpty(graghic.Style.IconStyle.Url))
                        {
                            Bitmap icon = null;
                            
                            System.Drawing.Point screenPoint = SpatialUtilities.ToScreenUnit(exportParameters.Extent, image.Width, image.Height, p.X, p.Y);

                            try
                            {
                                System.Net.HttpWebRequest request = ProxiedWebRequest.Create(graghic.Style.IconStyle.Url);

                                System.Net.HttpWebResponse response = (System.Net.HttpWebResponse)request.GetResponse();
                                if (response.StatusCode == System.Net.HttpStatusCode.OK)
                                {
                                    System.IO.Stream responseStream = response.GetResponseStream();
                                    icon = new Bitmap(responseStream);

                                    if (icon != null)
                                    {
                                        ArcGISLog.Write("Adding Image: " + graghic.Style.IconStyle.Url);
                                        markupGraphics.DrawImageUnscaled(icon, screenPoint);
                                    }
                                }
                            }
                            catch {
                                ArcGISLog.Write("Failed to load image: " + graghic.Style.IconStyle.Url);
                            }

                            MergeImage(image, graphicImage, fillOpacity);
                        }

                        break;
                    case "Polyline":
                        WorldViewSolutions.Geometry.Polyline polyline = (WorldViewSolutions.Geometry.Polyline)graghic.Geometry;

                        foreach (WorldViewSolutions.Geometry.Path mdPath in polyline.Paths)
                        {
                            for (int i = 0; i < mdPath.Points.Count - 1; i++)
                            {
                                AddPathSegment(image, path, exportParameters.Extent, mdPath.Points[i], mdPath.Points[i + 1]);
                            }
                        }
                        markupGraphics.DrawPath(strokePen, path);
                        MergeImage(image, graphicImage, strokeOpacity);
                        break;

                    case "Polygon":

                        WorldViewSolutions.Geometry.Polygon polygon = (WorldViewSolutions.Geometry.Polygon)graghic.Geometry;
                        ArcGISLog.Write("Polygon Ring Count: " + polygon.Rings.Count);

                        path = new GraphicsPath();
                        foreach (WorldViewSolutions.Geometry.Ring mdRing in polygon.Rings)
                        {
                            List<System.Drawing.Point> polygonPoints = new List<System.Drawing.Point>();
                            for (int i = 0; i < mdRing.Points.Count - 1; i++)
                            {
                                polygonPoints.Add(SpatialUtilities.ToScreenUnit(exportParameters.Extent, image.Width, image.Height, mdRing.Points[i].X, mdRing.Points[i].Y));
                            }
                            path.AddPolygon(polygonPoints.ToArray());
                        }



                        // fill and merge
                        markupGraphics.FillPath(fillBrush, path);
                        MergeImage(image, graphicImage, fillOpacity);

                        //create new image
                        graphicImage = new Bitmap(widthPixels, heightPixels);
                        graphicImage.SetResolution(exportParameters.DPI, exportParameters.DPI);

                        markupGraphics = System.Drawing.Graphics.FromImage(graphicImage);
                        markupGraphics.SmoothingMode = SmoothingMode.AntiAlias;

                        //stroke and merge
                        markupGraphics.DrawPath(strokePen, path);
                        MergeImage(image, graphicImage, strokeOpacity);

                        break;

                    case "Envelope":
                        WorldViewSolutions.Geometry.Envelope mdEnvelope = (WorldViewSolutions.Geometry.Envelope)graghic.Geometry;

                        p1 = SpatialUtilities.ToScreenUnit(exportParameters.Extent, image.Width, image.Height, mdEnvelope.XMin, mdEnvelope.YMax);
                        p2 = SpatialUtilities.ToScreenUnit(exportParameters.Extent, image.Width, image.Height, mdEnvelope.XMax, mdEnvelope.YMin);

                        rect = new Rectangle(p1.X, p1.Y, p2.X - p1.X, p2.Y - p1.Y);
                        markupGraphics.FillRectangle(fillBrush, rect);
                        MergeImage(image, graphicImage, fillOpacity);

                        graphicImage = new Bitmap(widthPixels, heightPixels);
                        graphicImage.SetResolution(exportParameters.DPI, exportParameters.DPI);

                        markupGraphics = System.Drawing.Graphics.FromImage(graphicImage);
                        markupGraphics.SmoothingMode = SmoothingMode.AntiAlias;

                        markupGraphics.DrawRectangle(strokePen, rect);
                        MergeImage(image, graphicImage, strokeOpacity);

                        break;
                }
            }
            return image;
        }

        /// <summary>
        /// Adds the path segment.
        /// </summary>
        /// <param name="image">The image.</param>
        /// <param name="path">The path.</param>
        /// <param name="extent">The extent.</param>
        /// <param name="fromPoint">From point.</param>
        /// <param name="toPoint">To point.</param>
        /// <remarks></remarks>
        private static void AddPathSegment(Bitmap image, GraphicsPath path, EnvelopeN extent, WorldViewSolutions.Geometry.Point fromPoint, WorldViewSolutions.Geometry.Point toPoint)
        {
            path.AddLine(SpatialUtilities.ToScreenUnit(extent, image.Width, image.Height, fromPoint.X, fromPoint.Y), SpatialUtilities.ToScreenUnit(extent, image.Width, image.Height, toPoint.X, toPoint.Y));
        }

        /// <summary>
        /// Merges a set of Bitmap images into a single Bitmap. Draws from bottom of list upwards.
        /// </summary>
        /// <param name="images">List of Bitmap images. Must all be same size.</param>
        /// <param name="transparencies">Transparency values for each image, for partial
        /// transparency to image underneath. Values must be between 0 and 1 (opaque).</param>
        /// <returns>Merge of bitmaps.</returns>
        /// <remarks></remarks>
        private static Bitmap MergeImages(List<Bitmap> images, List<float> transparencies)
		{
			//HttpContext.Current.Trace.Write("MapExport", "BEGIN: MergeImages()");

			//HttpContext.Current.Trace.Write("MapExport", "creating output image: width = " + images[0].exportParameters.Width + ", height = " + images[0].exportParameters.Height);
			Bitmap mergedImage = new Bitmap(images[0].Width, images[0].Height);
			mergedImage.SetResolution(images[0].HorizontalResolution, images[0].VerticalResolution);
			System.Drawing.Graphics graphics = System.Drawing.Graphics.FromImage(mergedImage);


			// merge images in reverse order
			for (int i = images.Count - 1; i >= 0; i--)
			//for (int i = 0; i < images.Count; i++)
			{
				Bitmap image = images[i];
				float opacity = transparencies[i];

				//in case 100 based opacity is sent in
				if (opacity > 1)
					opacity = opacity / 100;


				//HttpContext.Current.Trace.Write("MapExport", "Adding " + transparencies[i] + " opacity image " + image.Tag);
				// draw with transparency via alpha blending
				ColorMatrix cm = new ColorMatrix();
				cm.Matrix00 = cm.Matrix11 = cm.Matrix22 = cm.Matrix44 = 1;
				cm.Matrix33 = opacity;

				ImageAttributes imgAttributes = new ImageAttributes();
				imgAttributes.SetColorMatrix(cm);

				graphics.DrawImage(image,
					new Rectangle(0, 0, image.Width, image.Height),
					0, 0, image.Width, image.Height,
					GraphicsUnit.Pixel, imgAttributes);
				//}
			}

			graphics.Dispose();
			//HttpContext.Current.Trace.Write("MapExport", "END: MergeImages()");
			return mergedImage;
		}

        /// <summary>
        /// Merges the image.
        /// </summary>
        /// <param name="baseImage">The base image.</param>
        /// <param name="newImage">The new image.</param>
        /// <param name="opacity">The opacity.</param>
        /// <returns></returns>
        /// <remarks></remarks>
        private static Bitmap MergeImage(Bitmap baseImage, Bitmap newImage, float opacity)
		{
			System.Drawing.Graphics graphics = System.Drawing.Graphics.FromImage(baseImage);

			// draw with transparency via alpha blending
			ColorMatrix cm = new ColorMatrix();
			cm.Matrix00 = cm.Matrix11 = cm.Matrix22 = cm.Matrix44 = 1;
			cm.Matrix33 = opacity;

			ImageAttributes imgAttributes = new ImageAttributes();
			imgAttributes.SetColorMatrix(cm);

			graphics.DrawImage(newImage,
				new Rectangle(0, 0, newImage.Width, newImage.Height),
				0, 0, newImage.Width, newImage.Height,
				GraphicsUnit.Pixel, imgAttributes);

			graphics.Dispose();

			return baseImage;
		}

		#endregion

		#region [ Misc Private Methods ]

        /// <summary>
        /// Gets the encoder.
        /// </summary>
        /// <param name="format">The format.</param>
        /// <returns></returns>
        /// <remarks></remarks>
		private static ImageCodecInfo GetEncoder(System.Drawing.Imaging.ImageFormat format)
		{

			ImageCodecInfo[] codecs = ImageCodecInfo.GetImageDecoders();

			foreach (ImageCodecInfo codec in codecs)
			{
				if (codec.FormatID == format.Guid)
				{
					return codec;
				}
			}
			return null;
		}

		#endregion
	}
}
